package com.gator.spring.framework.webmvc.servlet;

import com.gator.spring.framework.annotation.PABLO_Controller;
import com.gator.spring.framework.annotation.PABLO_RequestMapping;
import com.gator.spring.framework.context.PABLO_ApplicationContext;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author PABLO
 * @Date 2022/5/8 13:29
 * @Desc
 */
@Slf4j
public class PABO_DispatcherServlet extends HttpServlet {

    private final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";

    //聚合容器
    private PABLO_ApplicationContext context;


    //缓存定位每个Method请求路径
    private List<PABLO_HandlerMapping> handlerMappings = new ArrayList<>();

    //缓存每个method对应的adapter
    private Map<PABLO_HandlerMapping, PABLO_HandlerAdapter> handlerAdapters = new HashMap<PABLO_HandlerMapping, PABLO_HandlerAdapter>();
    //缓存视图解析器，缓存页面
    private List<PABLO_ViewResolver> viewResolvers = new ArrayList<PABLO_ViewResolver>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatch(req, resp);
        } catch (Exception e) {
            try {
                processDispatchResult(req, resp, new PABLO_ModelAndView("500"));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            //resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "").replaceAll(",\\s", "\r\n"));
            e.printStackTrace();
        }
    }

    //最先执行
    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化ApplicationContext容器
        //com.gator.spring.framework.context.PABLO_ApplicationContext.refresh
        context = new PABLO_ApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
        //初始化Spring-MVC  九大组件
        initStrategies(context);
    }

    private void initStrategies(PABLO_ApplicationContext context) {
        //多文件上传的组件
        initMultipartResolver(context);
        //初始化本地语言环境
        initLocaleResolver(context);
        //初始化模板处理器
        initThemeResolver(context);
        //初始化handlerMapping  √
        initHandlerMappings(context);
        //初始化参数适配器  √
        initHandlerAdapters(context);
        //初始化异常拦截器
        initHandlerExceptionResolvers(context);
        //初始化视图预处理器
        initRequestToViewNameTranslator(context);
        //初始化视图解析器  √
        initViewResolvers(context);
        //参数缓存器
        initFlashMapManager(context);
    }

    /**
     * @Description: 每个handler都有一个adapter路由，通过req获取对应handlerMapping即Method的URL
     * 通过adapter将req正确的路由到某对应method上
     * @Author: PABLO
     * @Date: 2022/5/8 14:28
     * @Params: [context]
     * @Return: void
     **/
    private void initHandlerAdapters(PABLO_ApplicationContext context) {
        //将request请求转为一个具体handler的对应,并匹配参数
        //需先通过request携带的URiUI 定位到具体handler（Method）
        //每个请求都对应一个handler，每个请求都对应一个handlerAdapter，负责handler路由
        for (PABLO_HandlerMapping handlerMapping : this.handlerMappings) {
            this.handlerAdapters.put(handlerMapping, new PABLO_HandlerAdapter());
        }
    }

    //每个view元素对应一个视图如HTML
    private void initViewResolvers(PABLO_ApplicationContext context) {

        //拿到模板的存放目录,存放404...等页面
        //可设置多个目录，这里只设置了一个
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();

        //根据目录路径创建文件，集合的一个元素（view）对应一个模板（一个view类型）即可(即每个文件目录)，例如html、freemarker...
        File templateRootDir = new File(templateRootPath);
        String[] templates = templateRootDir.list();
        for (int i = 0; i < templates.length; i++) {
            //这里主要是为了兼容多模板，所有模仿Spring用List保存
            this.viewResolvers.add(new PABLO_ViewResolver(templateRoot));
        }

    }

    private void initRequestToViewNameTranslator(PABLO_ApplicationContext context) {
    }

    private void initHandlerExceptionResolvers(PABLO_ApplicationContext context) {
    }

    private void initFlashMapManager(PABLO_ApplicationContext context) {
    }

    /**
     * @Description: 初始化扫描所有Controller和其中method，将每个方法的请求路径保存起来
     * 供后续请求url匹配
     * @Author: PABLO
     * @Date: 2022/5/8 14:22
     * @Params: [context]
     * @Return: void
     **/
    private void initHandlerMappings(PABLO_ApplicationContext context) {
        String[] beanNames = context.getBeanDefinitionNames();
        try {
            for (String beanName : beanNames) {
                Object controller = context.getBean(beanName);
                Class<?> clazz = controller.getClass();
                if (!clazz.isAnnotationPresent(PABLO_Controller.class)) continue;
                String baseUrl = "";
                //获取Controller路径  如/web
                if (clazz.isAnnotationPresent(PABLO_RequestMapping.class)) {
                    PABLO_RequestMapping requestMapping = clazz.getAnnotation(PABLO_RequestMapping.class);
                    baseUrl = requestMapping.value();
                }
                //获取Method的url配置
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    //没有加RequestMapping注解的直接忽略
                    if (!method.isAnnotationPresent(PABLO_RequestMapping.class)) continue;
                    //映射URL
                    PABLO_RequestMapping requestMapping = method.getAnnotation(PABLO_RequestMapping.class);
                    String regex = ("/" + baseUrl + "/" + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");
                    Pattern pattern = Pattern.compile(regex);
                    //添加映射关系到handlerMapping
                    //注意：这里会添加两次，因为ApplicationContext中的definition中通过消协首字母类名和类.class都可以获取对象
                    //故一个对象其实有两个definition，如设置只能通过class获取对象或加判断逻辑等，这里将不会有重复pattern【自己把握】
                    this.handlerMappings.add(new PABLO_HandlerMapping(pattern, controller, method));
                }
            }

            //去重复
            handlerMappings = removeDuplicate(handlerMappings);
            System.out.println("------------Mapper info------------");
            handlerMappings.forEach(PABLO_HandlerMapping->
                    System.out.print("Controller->"+PABLO_HandlerMapping.getController().getClass().getSimpleName()+"    url->"+PABLO_HandlerMapping.getPattern().pattern()+ " \n")
            );


        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    private List<PABLO_HandlerMapping> removeDuplicate(List<PABLO_HandlerMapping> handlerMappings) {
        Set<PABLO_HandlerMapping> distance = new TreeSet<>(new Comparator<PABLO_HandlerMapping>() {
            @Override
            public int compare(PABLO_HandlerMapping o1, PABLO_HandlerMapping o2) {
                return o1.getPattern().pattern().compareTo(o2.getPattern().pattern());
            }
        });

        distance.addAll(handlerMappings);

        return new ArrayList<>(distance);
    }

    private void initThemeResolver(PABLO_ApplicationContext context) {
    }

    private void initLocaleResolver(PABLO_ApplicationContext context) {
    }

    private void initMultipartResolver(PABLO_ApplicationContext context) {
    }

    /**
     * @Description: 核心方法
     * @Author: PABLO
     * @Date: 2022/5/8 19:25
     * @Params: [req, resp]
     * @Return: void
     **/
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        //根据请求获得URL 返回对应handler
        PABLO_HandlerMapping handler = getHandler(req);
        //将mv转为response可输出的http支持的结果
        if (Objects.isNull(handler)) {
            processDispatchResult(req, resp, new PABLO_ModelAndView("404"));
            return;
        }
        //根据请求对应adapter，adapter和method是一一对应的，缓存在map中
        //为什么需要adapter，因为形参和实参需要转换，request请求可以通过handlerMapping定位method，但是
        //method的参数列表也需要和request请求的列表相对应，才能构建出invoke需要的参数列表
        PABLO_HandlerAdapter ha = getHandlerAdapter(handler);
        //返回mv对象
        PABLO_ModelAndView mv = ha.handle(req, resp, handler);
        //将mv转为response可输出的http支持的结果
        processDispatchResult(req, resp, mv);
    }

    private PABLO_HandlerAdapter getHandlerAdapter(PABLO_HandlerMapping handler) {
        if (this.handlerAdapters.isEmpty()) {
            return null;
        }
        PABLO_HandlerAdapter ha = this.handlerAdapters.get(handler);
        if (ha.supports(handler)) return ha;
        return null;
    }

    /**
     * @Description: 将mv转为response可输出的http支持的结果
     * @Author: PABLO
     * @Date: 2022/5/8 13:45
     * @Params: [req, resp, pablo_modelAndView]
     * @Return: void
     **/
    private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, PABLO_ModelAndView mv) throws Exception {
        if (Objects.isNull(mv)) return;
        //判断视图解析器，视图解析器中的所有页面文件在init时候就被加载进来了
        if (this.viewResolvers.isEmpty()) return;
        for (PABLO_ViewResolver viewResolver : this.viewResolvers) {
            PABLO_View view = viewResolver.resolveViewName(mv.getViewName(), null);
            //渲染
            view.render(mv.getModel(), req, resp);
            return;
        }
    }

    private PABLO_HandlerMapping getHandler(HttpServletRequest req) {

        if (this.handlerMappings.isEmpty()) return null;
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");

        for (PABLO_HandlerMapping handler : this.handlerMappings) {
            try {
                Matcher matcher = handler.getPattern().matcher(url);
                //如果没有匹配上继续下一个匹配
                if (!matcher.matches()) {
                    continue;
                }
                return handler;
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }


}
